<?php
/*
 * Copyright (C) 2010 TopCoder Inc., All Rights Reserved.
 */
namespace protocol\translation\framework\services;

use protocol\translation\framework\services\ConversionException;

/**
 * <p>
 * Helper class containing constants and methods for argument checking and other
 * common functionality.
 * </p>
 *
 * @author akinwale
 * @version 1.0
 */
final class Helper {
    /**
     * <p>
     * Represents the "clientCorrelator" string value.
     * </p>
     *
     * @var string
     */
    const CLIENT_CORRELATOR = "clientCorrelator";

    /**
     * <p>
     * Represents the "notifyURL" string value.
     * </p>
     *
     * @var string
     */
    const NOTIFY_URL = "notifyURL";

    /**
     * <p>
     * Represents the "callbackData" string value.
     * </p>
     *
     * @var string
     */
    const CALLBACK_DATA = "callbackData";

    /**
     * <p>
     * Represents the "send sms" string value.
     * </p>
     *
     * @var string
     */
    const SEND_SMS = "send sms";

    /**
     * <p>
     * Represents the "subscribeToSMSDeliveryNotifications" string value.
     * </p>
     *
     * @var string
     */
    const SUB_DELIV_NOTI = "subscribeToSMSDeliveryNotifications";

    /**
     * <p>
     * Represents the "subscribe to notifications" string value.
     * </p>
     *
     * @var string
     */
    const SUB_NOTI = "subscribe to notifications";

    /**
     * <p>
     * Represents the "query delivery status" string value.
     * </p>
     *
     * @var string
     */
    const QUERY_DELIV_STATUS = "query delivery status";

    /**
     * <p>
     * Represents the "Stop the subscription to delivery notifications" string value.
     * </p>
     *
     * @var string
     */
    const STOP_SUB_DELIV_NOTI = "Stop the subscription to delivery notifications";

    /**
     * <p>
     * Represents the "retrieve messages sent" string value.
     * </p>
     *
     * @var string
     */
    const RETRIEVE_MSGS_SENT = "retrieve messages sent";

    /**
     * <p>
     * Represents the "stop subscription" string value.
     * </p>
     *
     * @var string
     */
    const STOP_SUB = "stop subscription";

    /**
     * <p>
     * Private constructor to prevent instantiation.
     * </p>
     */
    private function __construct() {

    }

    /**
     * <p>
     * Method to check if the value with the specified parameter name has been unset.
     * </p>
     *
     * @param object $value the value to check
     * @param string $paramName the parameter name
     * @param string $methodName the name of the method
     * @param Log $logger the log instance
     *
     * @throws InvalidArgumentException if $value is null or an empty string
     */
    public static function checkIsSet($value, $paramName, $methodName = NULL, $logger = NULL) {
        if (!isset($value)) {
            throw self::logError($methodName, new \InvalidArgumentException("[$paramName] cannot be null"), $logger);
        }
    }

    /**
     * <p>
     * Method to check if the value with the specified parameter name is null or an empty string.
     * </p>
     *
     * @param string $value the string value to check
     * @param string $paramName the parameter name
     * @param string $methodName the name of the method
     * @param Log $logger the log instance
     *
     * @throws InvalidArgumentException if $value is not a string or is null or an empty string
     */
    public static function checkString($value, $paramName, $methodName = NULL, $logger = NULL) {
        self::checkIsSet($value, $paramName, $methodName, $logger);

        if (!\is_string($value)) {
            throw self::logError($methodName, new \InvalidArgumentException("[$paramName] is not a string"), $logger);
        }

        if (strlen(trim($value)) == 0) {
            throw self::logError($methodName, new \InvalidArgumentException(
                "[$paramName] cannot be an empty string"), $logger);
        }
    }

    /**
     * <p>
     * Method to check if the value with the specified parameter name is a valid array
     * and doesn't contain any unset or null elements.
     * </p>
     *
     * @param array $array the array to check
     * @param string $paramName the parameter name
     * @param string $methodName the name of the method
     * @param Log $logger the log instance
     *
     * @throws InvalidArgumentException if $value is null or an empty string
     */
    public static function checkArray($array, $paramName, $methodName = NULL, $logger = NULL) {
        self::checkIsSet($array, $paramName, $methodName, $logger);

        if (!is_array($array)) {
            throw self::logError($methodName, new \InvalidArgumentException(
                "[$paramName] should be an array"), $logger);
        }

        foreach ($array as $key => $value) {
            if ($value == null) {
                throw self::logError($methodName, new \InvalidArgumentException(
                    "[$paramName] cannot contain unset/null elements"), $logger);
            }
        }
    }

    /**
     * <p>
     * Method to log method entry.
     * </p>
     *
     * @param string $methodName the name of the method
     * @param array $methodParams the method parameters
     * @param Log $logger the Log instance
     */
    public static function logEntry($methodName, $methodParams = array(), $logger = NULL) {
        if ($logger != NULL) {
            $message  = "{Method Entry: $methodName}";
            if ($methodParams != NULL && count($methodParams) > 0) {
                $message .= "\n{Method Params: {";
                $idx = 0;
                foreach ($methodParams as $paramName => $paramValue) {
                    $message .= "[$paramName: " . print_r($paramValue, true) . "]";
                    if ($idx != count($methodParams) - 1) {
                        $message .= ", ";
                    }
                    $idx += 1;
                }
                $message .= "}";
            }
            $logger->info($message);
        }
    }

    /**
     * <p>
     * Method to log method exit.
     * <p>
     *
     * @param string $methodName the name of the method
     * @param mixed $returnValue the return value
     * @param int $startTime the start time of the method
     * @param Log $logger the Log instance
     */
    public static function logExit($methodName, $returnValue = NULL, $startTime, $logger = NULL) {
        // Calculate how long it took the method to execute
        $endTime = microtime();
        $mt_parts = explode(" ", $startTime);
        $now_parts = explode(" ", $endTime);
        $execTime = (($now_parts[0] - $mt_parts[0]) * 1000);

        if ($logger != NULL) {
            $message = "";
            if ($returnValue != NULL) {
                $message .= "{Method $methodName return value: " . print_r($returnValue, true) . "}\n";
            }
            $message = "{Method Exit: $methodName; Finished in {$execTime}ms}";
            $logger->info($message);
        }
    }

    /**
     * <p>
     * Method to log an error.
     * </p>
     *
     * @param string $methodName the name of the method
     * @param mixed $exception the exception that was thrown as a result of the error
     * @param Log $logger the Log instance
     */
    public static function logError($methodName, $exception, $logger = NULL) {
        if ($logger != NULL) {
            $stackTrace = get_class($exception) . "\n";
            $exceptionTrace = $exception->getTrace();
            $first = true;
            for ($i = 0; $i < count($exceptionTrace); $i++) {
                $stackTrace .= "\tat ";
                $exTrace = $exceptionTrace[$i];
                if (array_key_exists("class", $exTrace)) {
                    $stackTrace .= $exTrace["class"] . ".";
                }
                $stackTrace .= $exTrace["function"];
                if (array_key_exists("file", $exTrace) && array_key_exists("line", $exTrace)) {
                    $stackTrace .= "(" . @$exTrace["file"] . ":" . @$exTrace["line"] . ")";
                }
                $stackTrace .= "\n";
            }

            $message  = "{An error occurred at $methodName: " . $exception->getMessage() . "}\n";
            $message .= "Stack Trace:\n{$stackTrace}";
            $logger->error($message);
        }

        return $exception;
    }

    /**
     * <p>
     * Helper method to check if the specified operation is valid according to the specified
     * required parameters map.
     * </p>
     *
     * @param string $operation the operation name
     * @param array $reqParamsMap the map of valid operations to required parameters
     * @param string $methodName the name of the method to be used for logging
     * @param Logger $logger the logger instance to be used for logging
     */
    public static function checkOperation($operation, &$reqParamsMap, $methodName, $logger) {
        self::checkString($operation, "\$operation", $methodName, $logger);
        if (!array_key_exists($operation, $reqParamsMap)) {
            throw self::logError($methodName, new \InvalidArgumentException(
                "The specified operation [$operation] could not be handled"), $logger);
        }
    }

    /**
     * <p>
     * Helper method to parse the specified message body string from the format:
     * key1=value1&key2=value2&... into an associate array of the key-value pairs.
     * </p>
     *
     * @param string $messageBody the message body
     * @param string $methodName the method name to be used for logging
     * @param Log $logger the logger instance to be used for logging
     *
     * @return array the associative array parsed from the specified message body
     *
     * @throws ConversionException if the message body to be parsed is invalid
     */
    public static function parseMessageBody($messageBody, $methodName, $logger) {
        $parsedBody = array();
        $messageBodyParts = \explode("&", urldecode($messageBody));
        for ($i = 0; $i < count($messageBodyParts); $i++) {
            $messageBodyPart = trim($messageBodyParts[$i]);
            if (strlen($messageBodyPart) > 0) {
                $paramParts = \explode("=", $messageBodyPart);
                if (count($paramParts) != 2) {
                    throw self::logError($methodName, new ConversionException(
                        "Invalid message body: Expected 2 parts after splitting [$messageBodyPart] by '='"), $logger);
                }

                // Get the parameter name
                $paramKey = trim($paramParts[0]);

                // Since the value is URL encoded, we have to decode it before assigning
                $paramValue = trim($paramParts[1]);

                if (strlen($paramKey) == 0) {
                    throw self::logError($methodName, new ConversionException(
                        "Invalid message body: The param key after splitting [$messageBodyPart] is an empty string"),
                        $logger);
                }
                if (strlen($paramValue) == 0) {
                    throw self::logError($methodName, new ConversionException(
                        "Invalid message body: The param value after splitting [$messageBodyPart] is an empty string"),
                        $logger);
                }

                $parsedBody[$paramKey] = $paramValue;
            }
        }

        return $parsedBody;
    }
}

?>